from django.template import Context, loader, RequestContext
from django.template.loader import get_template
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.auth import logout, login, authenticate
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.core.serializers import serialize
from django.core.mail import send_mail
from django.utils import simplejson
from django import forms

# Create your views here.
from movie.vote.models import Movie
from movie.vote.models import Screening
from movie.vote.models import Rating

import datetime
import random
import string

STATIC_INDEX = '/static/org.mikrosopht.MovieVote/MovieVote.html'

# How many movies to show per page
DEFAULT_COUNT = 25

class NewUserForm(forms.Form):
  username = forms.CharField(max_length=30)
  email = forms.EmailField()

  def clean(self):
    # Check for username existence
    if 'username' not in self.cleaned_data or not self.cleaned_data['username']:
      raise forms.ValidationError('No username specified')
    if User.objects.filter(username=self.cleaned_data['username']):
      raise forms.ValidationError('A user already exists with that name')

    # Check for email existence
    if User.objects.filter(email=self.cleaned_data['email']):
      raise forms.ValidationError('A user already exists with that email address')
    return self.cleaned_data

class ChangePasswordForm(forms.Form):
  password1 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='New Password')
  password2 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='New Password (again)')

  def clean(self):
    """Make sure password fields match."""
    if 'password1' not in self.cleaned_data or 'password2' not in self.cleaned_data:
      raise forms.ValidationError('Password not specified')
    if not self.cleaned_data['password1']:
      raise forms.ValidationError('Password cannot be blank')
    if self.cleaned_data['password1'] != self.cleaned_data['password2']:
      raise forms.ValidationError('Passwords do not match')
    return self.cleaned_data

class ForgotPasswordForm(forms.Form):
  email = forms.EmailField()

  def clean(self):
    """Make sure email exists in db."""
    if 'email' not in self.cleaned_data:
      raise forms.ValidationError('Email address not entered')
    email = self.cleaned_data['email']
    if not User.objects.filter(email=email):
      raise forms.ValidationError('No account with that address')
    return self.cleaned_data

class UIMovie(object):
  def __init__(self, movie, user):
    self.movie = movie
    self.voter_count = movie.voters.count()
    self.downvoter_count = movie.downvoters.count()
    self.voted = movie.voters.filter(username=user.username).count()
    self.downvoted = movie.downvoters.filter(username=user.username).count()
    self.screenings = Screening.objects.filter(movie=movie).count()
    try:
      self.rating = Rating.objects.filter(movie=movie, user=user)[0].value
    except IndexError:
      self.rating = 0

  def _get_tally_detail(self):
    screening_stat = ''
    if self.screenings:
      screening_stat = '/S=%s' % (self.screenings,)
    return '(+%s/-%s%s)' % (self.voter_count, self.downvoter_count,
                            screening_stat)

  def to_dict(self):
    movie_dict = {}
    for field in ('name', 'imdb_url', 'rotten_url', 'metacritic_url', 
                  'review_average_score', 'youtube_trailer_url', 'date_added'):
      movie_dict[field] = str(getattr(self.movie, field))
    movie_dict['screenings'] = str(self.screenings)
    movie_dict['voter_count'] = str(self.voter_count)
    movie_dict['downvoter_count'] = str(self.downvoter_count)
    movie_dict['voted'] = str(self.voted)
    movie_dict['downvoted'] = str(self.downvoted)
    movie_dict['rating'] = str(self.rating)
    movie_dict['tally_detail'] = self._get_tally_detail()
    movie_dict['tally'] = '%.2f' % (float(self.movie.tally,))
    return movie_dict

def set_preference(request, name, value):
  request.session[name] = value

def get_preference(request, name, default=None, cast=None):
  pref = default
  if name in request.GET:
    pref = request.GET[name]
  elif name in request.session:
    pref = request.session[name]
  if cast:
    try:
      pref = cast(pref)
    except (TypeError, ValueError):
      pref = default
  return pref

def random_string(length):
  alphanum = string.letters + string.digits
  return ''.join([x for i in range(length) for x in random.choice(alphanum)])

def get_movie_count(movie_like=None):
  if movie_like:
    count = Movie.objects.filter(pk__icontains=movie_like).count()
  else:
    count = Movie.objects.count()
  return count

def get_unrated_screening_count(user):
  rated_movies = set([r.movie for r in Rating.objects.filter(user=user).all()])
  unrated = 0
  for screening in Screening.objects.all():
    if user not in screening.attendees.all():
      continue
    if screening.movie not in rated_movies:
      unrated += 1
  return unrated

def get_movies_by_screening(user, movie_search=None, start=0, count=1):
  movies = []
  if movie_search:
    screenings = Screening.objects.filter(movie__name__icontains=movie_search).order_by('-date')
  else:
    screenings = Screening.objects.order_by('-date')
  for screening in screenings:
    # Skip movies not attended
    if not screening.attendees.filter(username=user.username).count():
      continue
    movies.append(UIMovie(screening.movie, user))
  return len(movies), movies[start:(count + start)]
    
def get_movies_for_ui(user, sort_criteria=None, movie_name=None, movie_like=False, start=0, count=1):
  movies = Movie.objects
  if sort_criteria:
    movies = movies.order_by(*sort_criteria)
  if movie_name:
    if movie_like:
      movies = movies.filter(pk__icontains=movie_name)
    else:
      movies = movies.filter(pk=movie_name)
  movies = movies.filter(votable=True)
  movies = movies[start:(count + start)]
  return [UIMovie(movie, user) for movie in movies]

def index_redirect(request):
  return HttpResponseRedirect(STATIC_INDEX)

def logout(request):
  return HttpResponseRedirect(reverse('django.contrib.auth.views.logout_then_login'))

@login_required
def index(request):
  allowed_sort_keys = ('tally', 'name', 'date_added', 'review_average_score', 'screening_date')

  sort1 = get_preference(request, 'sort1', '-tally')
  sort2 = get_preference(request, 'sort2', 'name')
  # Some error checking as a user can lock himself out if he munges the fields
  if sort1.lstrip('-') not in allowed_sort_keys:
    sort1 = '-tally'
  if sort2.lstrip('-') not in allowed_sort_keys:
    sort2 = 'name'

  set_preference(request, 'sort1', sort1)
  set_preference(request, 'sort2', sort2)

  sort_criteria = []
  if sort1:
    sort_criteria.append(sort1)
    # Use a secondary sort by non-weighted tallies when sorting by tally
    if sort1 == '-tally':
      sort_criteria.append('-tally_noweight')
  if sort2:
    sort_criteria.append(sort2)
  
  start = get_preference(request, 'start', 0, int)
  count = get_preference(request, 'count', DEFAULT_COUNT, int)

  # Movie search
  search_name = get_preference(request, 'namelike', '')
  if sort1 == 'screening_date':
    movie_count, movies = get_movies_by_screening(request.user, movie_search=search_name,
                                                  start=start, count=count)
  else:
    movies = get_movies_for_ui(request.user, sort_criteria, movie_name=search_name,
                               movie_like=True, start=start, count=count)
    movie_count = get_movie_count(search_name)

  json = {}
  json['count'] = str(movie_count)
  json['movies'] = [movie.to_dict() for movie in movies]
  return HttpResponse(simplejson.dumps(json), mimetype='application/json')

@login_required
def get_voters(request, movie_name):
  movie = get_object_or_404(Movie, pk=movie_name)
  voters = movie.voters.order_by('username')
  downvoters = movie.downvoters.order_by('username')
  vote_info = {}
  vote_info['voters'] = []
  vote_info['downvoters'] = []
  vote_info['screenings'] = []
  for voter in voters:
    vote_detail = {}
    vote_detail['username'] = voter.username
    vote_detail['weight'] = movie.get_weight(voter)
    vote_info['voters'].append(vote_detail)
  for voter in downvoters:
    vote_detail = {}
    vote_detail['username'] = voter.username
    vote_detail['weight'] = movie.get_weight(voter)
    vote_info['downvoters'].append(vote_detail)
  for screening in movie.screening_set.order_by('-date'):
    vote_detail = {}
    vote_detail['attendees'] = [x.username for x in screening.attendees.order_by('username')]
    vote_detail['date'] = str(screening.date)
    vote_info['screenings'].append(vote_detail)
  return HttpResponse(simplejson.dumps(vote_info), mimetype='application/json')

@login_required
def user_stats(request, user=None):
  if user:
    user = get_object_or_404(User, username=user)
  else:
    user = request.user
  return Movie.VOTING_ALGORITHM().user_stats(user)

@login_required
def get_user_info(request):
  user_info = {}
  user_info['username'] = request.user.username
  user_info['sort1'] = get_preference(request, 'sort1', '-tally')
  user_info['sort2'] = get_preference(request, 'sort2', 'name')
  user_info['weighting_description'] = str(Movie.VOTING_ALGORITHM())
  user_info['unrated_screenings'] = str(get_unrated_screening_count(request.user))
  return HttpResponse(simplejson.dumps(user_info), mimetype='application/json')

@login_required
def update_tallies(request):
  Movie.update_all_tallies()
  return HttpResponseRedirect(request.META.get('HTTP_REFERER', STATIC_INDEX))

@login_required
def minus_vote(request, movie_name):
  movie = get_object_or_404(Movie, pk=movie_name)
  voters = movie.voters.filter(username=request.user.username)
  if voters:
    movie.voters.remove(request.user)
  else:
    movie.downvoters.add(request.user)
  movie.update_tally()
  return HttpResponse(simplejson.dumps(UIMovie(movie, request.user).to_dict()), mimetype='application/json')

@login_required
def plus_vote(request, movie_name):
  movie = get_object_or_404(Movie, pk=movie_name)
  downvoters = movie.downvoters.filter(username=request.user.username)
  if downvoters:
    movie.downvoters.remove(request.user)
  else:
    movie.voters.add(request.user)
  movie.update_tally()
  return HttpResponse(simplejson.dumps(UIMovie(movie, request.user).to_dict()), mimetype='application/json')

@login_required
def setrating(request, movie_name, value):
  movie = get_object_or_404(Movie, pk=movie_name)
  Rating.objects.filter(movie=movie, user=request.user).delete()
  value = int(value)
  if value > 0 and value <= 5:
    rating = Rating(movie=movie, user=request.user, value=value)
    rating.save()
  return HttpResponse()

@login_required
def password_change(request):
  if request.method == 'POST':
    form = ChangePasswordForm(request.POST)
    if form.is_valid():
      request.user.set_password(form.cleaned_data['password1'])
      request.user.save()
      return HttpResponseRedirect(STATIC_INDEX)
  else:
    form = ChangePasswordForm()
  return render_to_response('registration/change_password.html', {'form': form})

def new_user(request):
  if request.method == 'POST':
    form = NewUserForm(request.POST)
    if form.is_valid():
      random_passwd = User.objects.make_random_password()
      user = User.objects.create_user(
        form.cleaned_data['username'],
        form.cleaned_data['email'],
        random_passwd)
      user.set_password(random_passwd)
      user.save()
      login(request, authenticate(username=user.username, password=random_passwd))
      return render_password_change(request, user)
  else:
    form = NewUserForm()
  return render_to_response('registration/new_user.html', {'form': form})

def render_password_change(request, user):
  id = random_string(40)
  user.message_set.create(message=id)
  url = request.build_absolute_uri('/accounts/password_reset/%s/%s/' %
                                   (user.username, id))
  t = loader.get_template('registration/password_reset_email.txt')
  c = { 'url': url, 'username': user.username }
  send_mail('Movie Vote password reset', t.render(Context(c)), None, [user.email])
  return render_to_response('registration/forgot_password_done.html')

def forgot_password(request):
  if request.method == 'POST':
    form = ForgotPasswordForm(request.POST)
    if form.is_valid():
      user = User.objects.filter(email=form.cleaned_data['email'])[0]
      return render_password_change(request, user)
  else:
    form = ForgotPasswordForm()
  return render_to_response('registration/forgot_password.html', {'form': form})

def password_reset(request, user, id):
  user = get_object_or_404(User, username=user)
  if id not in user.get_and_delete_messages():
    # No ID found
    return render_to_response('registration/forgot_password_fail.html')

  # Set a random password, login, then redirect to change password form
  random_passwd = User.objects.make_random_password()
  user.set_password(random_passwd)
  user.save()
  login(request, authenticate(username=user.username, password=random_passwd))
  return HttpResponseRedirect(reverse('movie.vote.views.password_change'))
